feat(dashboards): Add DashboardRevision model and migration#112929
feat(dashboards): Add DashboardRevision model and migration#112929
Conversation
Add DashboardRevision model to store snapshots of dashboard state at the time of each edit. Each revision records the dashboard title, source action, a JSON snapshot of the full dashboard state, schema version, and the user who made the change. Includes a composite index on (dashboard_id, date_added DESC) to support efficient per-dashboard history lookups. Refs DAIN-1515 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Organization is already reachable via dashboard.organization_id. The direct FK was unnecessary denormalization with no query benefit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
This PR has a migration; here is the generated SQL for for --
-- Create model DashboardRevision
--
CREATE TABLE "sentry_dashboardrevision" ("id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "created_by_id" bigint NULL, "date_updated" timestamp with time zone NOT NULL, "date_added" timestamp with time zone NOT NULL, "title" varchar(255) NOT NULL, "source" varchar(32) NOT NULL, "snapshot" text NOT NULL, "snapshot_schema_version" integer NOT NULL, "dashboard_id" bigint NOT NULL);
ALTER TABLE "sentry_dashboardrevision" ADD CONSTRAINT "sentry_dashboardrevi_dashboard_id_7b943d04_fk_sentry_da" FOREIGN KEY ("dashboard_id") REFERENCES "sentry_dashboard" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTER TABLE "sentry_dashboardrevision" VALIDATE CONSTRAINT "sentry_dashboardrevi_dashboard_id_7b943d04_fk_sentry_da";
CREATE INDEX CONCURRENTLY "sentry_dashboardrevision_created_by_id_f69f0d9a" ON "sentry_dashboardrevision" ("created_by_id");
CREATE INDEX CONCURRENTLY "sentry_dashboardrevision_dashboard_id_7b943d04" ON "sentry_dashboardrevision" ("dashboard_id");
CREATE INDEX CONCURRENTLY "sentry_dashrev_dash_date_idx" ON "sentry_dashboardrevision" ("dashboard_id", "date_added" DESC); |
Backend Test FailuresFailures on
|
…frastructure New models with RelocationScope.Organization that reference a user require three additions: fixture creation in create_exhaustive_organization, inclusion in the user merge model_list, and coverage in the expect_models decorators. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wedamija
left a comment
There was a problem hiding this comment.
mostly lgtm, just a few comments
src/sentry/models/dashboard.py
Outdated
|
|
||
|
|
||
| @cell_silo_model | ||
| class DashboardRevision(Model): |
There was a problem hiding this comment.
Let's use DefaultFieldsModel here to get date_added and date_updated for free
src/sentry/models/dashboard.py
Outdated
| title = models.CharField(max_length=255) | ||
| source = models.CharField(max_length=32, default="edit") | ||
| snapshot: models.Field[dict[str, Any], dict[str, Any]] = JSONField(default=dict) | ||
| snapshot_schema_version = models.IntegerField(db_default=1) |
There was a problem hiding this comment.
Nit: Wondering if it makes sense to not have a default here, so that we're always explicit about which schema we're saving
src/sentry/models/dashboard.py
Outdated
|
|
||
|
|
||
| @cell_silo_model | ||
| class DashboardRevision(Model): |
There was a problem hiding this comment.
Will we automatically delete these once they're old enough, or will we keep them around until the Dashboard is deleted?
There was a problem hiding this comment.
We will have a limit per dashboard. New revisions above that will delete the oldest one. We'll start with 10 and adjust if necessary.
There was a problem hiding this comment.
We could use "source" to differentiate edits vs ai edits.
Gets date_added (auto_now_add) and date_updated (auto_now) for free, removes the manually declared date_added field. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Force callers to be explicit about which schema version they are saving rather than silently defaulting to 1. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend Test FailuresFailures on
|
The exhaustive backup test exports and re-imports data, then compares JSON field-by-field. The auto_now/auto_now_add fields date_added and date_updated are re-stamped at import time, causing a spurious UnequalJSON ComparatorFinding. Register a DateUpdatedComparator so the backup infrastructure treats these fields as "may be later" rather than requiring exact equality. Co-Authored-By: Claude Sonnet 4 <noreply@example.com>
DominikB2014
left a comment
There was a problem hiding this comment.
lgtm! just one question about one of the fields
| created_by_id = HybridCloudForeignKey( | ||
| "sentry.User", db_index=True, null=True, on_delete="SET_NULL" |
There was a problem hiding this comment.
I'm assuming this is how we know who made the revision, will we just assume null created_by_id is ai? or will created_by_id just be the id of the user who prompted the ai?
If it's the later, it might be useful to know wether the revision is ai created or user created.
There was a problem hiding this comment.
It will be the user that initiated/confirmed the changes made by the agent (since it doesn't save without confirming).
Add the
DashboardRevisionmodel and its migration as the storage layer for the Dashboard Revisions feature, which will give users the ability to revert a dashboard to a previous version.Each revision captures a point-in-time snapshot of a dashboard at the time of an edit, including:
snapshot— full JSON representation of the dashboard state (text-backed field)snapshot_schema_version— integer version of the snapshot schema, so future schema changes can be handled during restore without a full backfilltitle— dashboard title at the time of the revisionsource— what triggered the save (e.g."edit")created_by_id— the user who made the change (nullable,SET_NULLon user delete)Dashboard(CASCADE)A composite index on
(dashboard_id, date_added DESC)supports the primary query for fetching a dashboard's history, ordered newest-first.Refs DAIN-1515